﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.UI;
using System.Xml;

namespace Utils.MVC
{
    public class JQueryValidations
    {
        private static Dictionary<string, string> s_default_messages;

        private readonly Dictionary<string, Dictionary<string, string>> m_messages =
            new Dictionary<string, Dictionary<string, string>>();

        private readonly Dictionary<string, Dictionary<string, object>> m_rules =
            new Dictionary<string, Dictionary<string, object>>();

        private readonly Dictionary<string, string> m_rulesToAdd =
            new Dictionary<string, string>();

        private int m_addedRules;
        private string m_selector;

        public JQueryValidations(Page page)
        {
            if (s_default_messages == null)
            {
                var dic = new Dictionary<string, string>();
                var xmlDoc = new XmlDocument();
                try
                {
                    xmlDoc.Load(HttpContext.Current.Server.MapPath("~/bin/DefaultErrorMessage.xml"));

                    XmlNode errorMessages = xmlDoc.GetElementsByTagName("ErrorMessages")[0];

                    foreach (XmlNode node in errorMessages.ChildNodes)
                    {
                        if (node.Attributes == null) continue;
                        if (node.Attributes["rule"] == null || node.Attributes["message"] == null ||
                            node.Attributes["rule"].Value.IsNullOrEmpty())
                            continue;
                        dic.Add(node.Attributes["rule"].Value, node.Attributes["message"].Value.IfNull(string.Empty));
                    }

                    if (!dic.ContainsKey("default"))
                    {
                        dic.Add("default", "*");
                    }
                }
                catch
                {
                }

                s_default_messages = dic;
            }
            Page = page;
        }

        public Page Page { get; private set; }

        public void Required(string name)
        {
            AddRuleAndMessage(name, "required", true, null);
        }

        public void Required(string name, string message)
        {
            AddRuleAndMessage(name, "required", true, message);
        }

        public void Email(string name)
        {
            AddRuleAndMessage(name, "email", true, null);
        }

        public void Email(string name, string message)
        {
            AddRuleAndMessage(name, "email", true, message);
        }

        public void Number(string name)
        {
            AddRuleAndMessage(name, "number", true, null);
        }

        public void Number(string name, string message)
        {
            AddRuleAndMessage(name, "number", true, message);
        }

        public void Date(string name)
        {
            AddRuleAndMessage(name, "dateISO", true, null);
        }

        public void Date(string name, string message)
        {
            AddRuleAndMessage(name, "dateISO", true, message);
        }

        public void Range(string name, int min, int max)
        {
            AddRuleAndMessage(name, "range", new[] { min, max }, string.Format(s_default_messages["range"], min, max));
        }

        public void Range(string name, int min, int max, string message)
        {
            AddRuleAndMessage(name, "range", new[] { min, max }, message);
        }

        public void RangeLength(string name, int min, int max)
        {
            AddRuleAndMessage(name, "rangelength", new[] { min, max },
                              string.Format(s_default_messages["rangelength"], min, max));
        }

        public void RangeLength(string name, int min, int max, string message)
        {
            AddRuleAndMessage(name, "rangelength", new[] { min, max }, message);
        }

        public void EngOrNo(string name)
        {
            Regex(name, "EngOrNo", "[a-zA-Z0-9]+", null);
        }

        public void EngOrNo(string name, string message)
        {
            Regex(name, "EngOrNo", "[a-zA-Z0-9]+", message);
        }

        public void HalfHira(string name, string message)
        {
            Regex(name, "HalfHira", @"[\uff66-\uff9f]+", message);
        }

        public void HalfHira(string name)
        {
            Regex(name, "HalfHira", @"[\uff66-\uff9f]+", null);
        }

        public void Regex(string name, string regex)
        {
            Regex(name, regex, null);
        }

        public void Regex(string name, string regex, string message)
        {
            Regex(name, null, regex, message);
        }

        public void Regex(string name, string ruleName, string regex, string message)
        {
            if (name.IsNullOrEmpty() || regex.IsNullOrEmpty())
                return;
            if (!m_rulesToAdd.ContainsKey(regex))
            {
                ruleName = ruleName.IfNull("regex_" + m_rulesToAdd.Count);
                m_rulesToAdd.Add(regex, ruleName);
            }
            else
            {
                ruleName = m_rulesToAdd[regex];
            }

            AddRuleAndMessage(name, ruleName, true, message);
        }

        private void AddRuleAndMessage(string name, string rule, object value, string message)
        {
            if (name.IsNullOrEmpty())
                return;
            if (!m_rules.ContainsKey(name))
                m_rules[name] = new Dictionary<string, object>();
            m_rules[name][rule] = value;

            if (message.IsNullOrEmpty())
            {
                message = s_default_messages.ContainsKey(rule) ? s_default_messages[rule] : s_default_messages["default"];
            }
            else
            {
                if (!s_default_messages.ContainsKey(rule))
                {
                    s_default_messages[rule] = message;
                }
            }
            if (!m_messages.ContainsKey(name))
                m_messages[name] = new Dictionary<string, string>();
            m_messages[name][rule] = message;
        }

        public JQueryValidations SetSelector(string selector)
        {
            m_selector = selector;
            return this;
        }

        public string ToScripts()
        {
            return ToScripts(null);
        }
        public string ToScripts(string selector)
        {
            var builder = new StringBuilder();

            if (selector.IsNullOrEmpty())
            {
                selector = m_selector.IfNull("from");
            }

            //添加的Regex验证规则
            if (m_rulesToAdd.Count > 0)
            {
                builder.AppendFormat(
                    @"jQuery.validator.addMethod(""{0}"", function(value, element) {{
		                    return this.optional(element) || /^{1}$/.test(value);
	                    }}, ""*""
                    );",
                    m_rulesToAdd.ElementAt(m_addedRules).Value, m_rulesToAdd.ElementAt(m_addedRules).Key);
            }


            builder.Append("$(document).ready(function() {$(");
            builder.Append(selector.ToJson());
            builder.Append(").validate(");
            builder.Append(new
                    {
                        rules = m_rules,
                        messages = m_messages,
                        onkeyup = false
                    }.ToJson());
            builder.Append(");});");

            return builder.ToString();
        }

        public ValidationElement Element(string name)
        {
            return new ValidationElement(name, this);
        }
    }

    public class ValidationElement
    {
        public ValidationElement(string name, JQueryValidations validations)
        {
            Name = name;
            Validations = validations;
        }

        public string Name { get; private set; }

        public JQueryValidations Validations { get; private set; }

        public ValidationElement Required(string message)
        {
            Validations.Required(Name, message);
            return this;
        }

        public ValidationElement Email(string message)
        {
            Validations.Email(Name, message);
            return this;
        }


        public ValidationElement Number(string message)
        {
            Validations.Number(Name, message);
            return this;
        }

        public ValidationElement Date(string message)
        {
            Validations.Date(Name, message);
            return this;
        }

        public ValidationElement Range(int min, int max, string message)
        {
            Validations.Range(Name, min, max, message);
            return this;
        }

        public ValidationElement Regex(string regex, string message)
        {
            Validations.Regex(Name, regex, message);
            return this;
        }

        public ValidationElement RangeLength(int min, int max, string message)
        {
            Validations.RangeLength(Name, min, max, message);
            return this;
        }

        public ValidationElement EngOrNo(string message)
        {
            Validations.EngOrNo(Name, message);
            return this;
        }

        public ValidationElement HalfHira(string message)
        {
            Validations.HalfHira(Name, message);
            return this;
        }

        public ValidationElement Required()
        {
            Validations.Required(Name);
            return this;
        }

        public ValidationElement Email()
        {
            Validations.Email(Name);
            return this;
        }


        public ValidationElement Number()
        {
            Validations.Number(Name);
            return this;
        }

        public ValidationElement Date()
        {
            Validations.Date(Name);
            return this;
        }

        public ValidationElement Range(int min, int max)
        {
            Validations.Range(Name, min, max);
            return this;
        }

        public ValidationElement Regex(string regex)
        {
            Validations.Regex(Name, regex);
            return this;
        }

        public ValidationElement RangeLength(int min, int max)
        {
            Validations.RangeLength(Name, min, max);
            return this;
        }

        public ValidationElement EngOrNo()
        {
            Validations.EngOrNo(Name);
            return this;
        }

        public ValidationElement HalfHira()
        {
            Validations.HalfHira(Name);
            return this;
        }

        public override string ToString()
        {
            return string.Format("<script type=\"text/javascript\">{0}</script>", Validations.ToScripts());
        }
    }

    public static class JQueryValidationExtensions
    {
        public static JQueryValidations ClientValidations(this Page page)
        {
            return ClientValidations(page, "(default)");
        }

        public static JQueryValidations ClientValidations(this Page page, string validatioNname)
        {
            string key = typeof(JQueryValidations) + "+" + validatioNname;
            var validations = page.Items[key] as JQueryValidations;

            if (validations == null)
            {
                page.Items[key] = validations = new JQueryValidations(page);

                page.Unload += (ss, ee) =>
                                   {
                                       string script = page.ClientValidations().ToScripts("form");
                                       HttpContext.Current.Response.Write(
                                           string.Format("<script type=\"text/javascript\">{0}</script>", script));
                                   };
            }

            return validations;
        }

        public static ValidationElement ValidationsFor<T>(this ViewPage<T> page, Expression<Func<T, object>> expression)
        {
            string name = "_no_this_member_";
            if (expression.Body is MemberExpression)
            {
                name = ((MemberExpression)expression.Body).Member.Name;
            }

            return page.ClientValidations().Element(name);
        }
    }
}